
use serde::{Deserialize, Serialize};
use crate::peer::Peer;
use crate::torrent::Torrent;

// Request to the tracker to get info about peers
#[derive(Debug, Clone, Serialize)]
struct TrackerRequest {
    // urlencoded 20-byte string used as a unique ID for the client, generated by the client at startup.
    peer_id: String,
    // The port number that the client is listening on. Ports reserved for BitTorrent are typically 6881-6889.
    port: i32,
    // The total amount uploaded (since the client sent the 'started' event to the tracker) in base ten ASCII.
    uploaded: i32,
    // The total amount downloaded (since the client sent the 'started' event to the tracker) in base ten ASCII.
    downloaded: i32,
    // The number of bytes this client still has to download in base ten ASCII.
    left: i32,
    // Setting this to 1 indicates that the client accepts a compact response.
    compact: i32,
}

// Response to the Tracker Request, containing list of peers
#[derive(Debug, Clone, Deserialize)]
#[allow(dead_code)]
struct TrackerResponse {
    // Interval in seconds that the client should wait between sending regular requests to the tracker
    pub interval: usize,
    // Minimum announce interval. If present clients must not reannounce more frequently than this.
    #[serde(rename = "min interval")]
    pub min_interval: usize,
    // number of peers with the entire file, i.e. seeders (integer)
    pub complete: usize,
    // number of non-seeder peers, aka "leechers" (integer)
    pub incomplete: usize,
    /*
     (binary model) Instead of using the dictionary model described above,
     the peers value may be a string consisting of multiples of 6 bytes.
     First 4 bytes are the IP address and last 2 bytes are the port number. All in network (big endian) notation.
     */
    #[serde(with = "serde_bytes")]
    pub peers: Vec<u8>,
}

// Get list of Tracker Peers
pub async fn get_peers(torrent: &Torrent) -> anyhow::Result<Vec<Peer>> {
    /*
    Let's say the hexadecimal representation of our info hash is d69f91e6b2ae4c542468d1073a71d4ea13879a7f
    This 40 character long string was representing 20 bytes, so each character pair corresponds to a byte
    We can just put a % before each byte so the URL-encoded representation would be:
    %d6%9f%91%e6%b2%ae%4c%54%24%68%d1%07%3a%71%d4%ea%13%87%9a%7f
     */
    let info_hash: String = hex::encode(torrent.info_hash).chars().collect::<Vec<char>>().chunks(2).
        map(|byte| "%".to_owned() + String::from_iter(byte).as_str()).collect();
    let request = TrackerRequest {
        peer_id: String::from("00112233445566778899"),
        port: 6881,
        uploaded: 0,
        downloaded: 0,
        left: torrent.meta.info.length,
        compact: 1,
    };

    let url_params =
        serde_urlencoded::to_string(&request)?;
    let url = format!("{}?{}&info_hash={}", torrent.meta.announce, url_params, info_hash);
    println!("{}", url);
    let response = reqwest::get(url).await?;
    let response = response.bytes().await?;
    let tracker_response: TrackerResponse = serde_bencode::from_bytes(&response)?;
    let peers: Vec<Peer> = tracker_response.peers.chunks(6)
        .map(|peer| Peer::from(peer)).collect();
    Ok(peers)
}

pub fn get_piece_hash(piece: i32, torrent: &Torrent) -> [u8; 20] {
    let hashes: Vec<[u8; 20]> = get_pieces(torrent);
    hashes[piece as usize]
}

pub fn get_pieces(torrent: &Torrent) -> Vec<[u8; 20]> {
    let pieces = torrent.meta.info.pieces.chunks(20).map(|piece| piece.to_vec().try_into().unwrap()).collect();
    pieces
}